package cn.lingyangwl.agile.food.core.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.lingyangwl.agile.common.security.utils.SecurityUtils;
import cn.lingyangwl.agile.food.core.manager.*;
import cn.lingyangwl.agile.food.core.model.rqrs.goods.*;
import cn.lingyangwl.agile.food.model.entity.*;
import cn.lingyangwl.agile.food.model.rqrs.goods.*;
import cn.lingyangwl.agile.model.constants.*;
import cn.lingyangwl.agile.model.module.auth.AuthUserTenant;
import cn.lingyangwl.agile.model.module.auth.LoginUser;
import cn.lingyangwl.agile.model.module.tenant.TenantContext;
import cn.lingyangwl.agile.food.core.assembly.GoodsAssembly;
import cn.lingyangwl.agile.food.core.assembly.GoodsSkuAssembly;
import cn.lingyangwl.agile.food.core.mapper.GoodsMapper;
import cn.lingyangwl.agile.food.model.constants.StockInOutStatus;
import cn.lingyangwl.agile.food.model.constants.StockInOutType;
import cn.lingyangwl.agile.food.core.model.rqrs.cart.CartListReq;
import cn.lingyangwl.agile.food.model.rqrs.sku.SkuBase;
import cn.lingyangwl.agile.food.model.rqrs.sku.SkuReq;
import cn.lingyangwl.agile.food.model.rqrs.sku.SkuResp;
import cn.lingyangwl.agile.food.core.service.GoodsService;
import cn.lingyangwl.agile.food.core.service.GoodsSkuService;
import cn.lingyangwl.agile.food.core.service.StoreService;
import cn.lingyangwl.agile.tenant.core.utils.TenantUtils;
import cn.lingyangwl.framework.core.utils.spring.SpringUtils;
import cn.lingyangwl.framework.data.mate.annotations.FieldBindHandle;
import cn.lingyangwl.framework.lock.manager.ILockManager;
import cn.lingyangwl.framework.lock.model.ILock;
import cn.lingyangwl.framework.mybatis.plus.utils.PageUtil;
import cn.lingyangwl.framework.tool.core.CollectionUtils;
import cn.lingyangwl.framework.tool.core.exception.BizException;
import cn.lingyangwl.framework.validation.ValidationUtils;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yitter.idgen.YitIdHelper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.validation.Validator;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 商品
 *
 * @author shenguangyang
 */
@Slf4j
@Service
public class GoodsServiceImpl extends ServiceImpl<GoodsMapper, Goods>
    implements IService<Goods>, GoodsService {

    @Resource
    private StoreGoodsManager storeGoodsManager;
    @Resource
    private GoodsAssembly goodsAssembly;
    @Resource
    private GoodsSpecValueManager goodsSpecValueManager;
    @Resource
    private GoodsSkuAssembly goodsSkuAssembly;
    @Resource
    private GoodsSkuManager goodsSkuManager;
    @Resource
    private StockDocumentManager stockDocumentManager;
    @Resource
    private StockDocumentDetailManager stockDocumentDetailManager;
    @Resource
    private GoodsCategoryManager categoryManager;
    @Resource
    private StoreManager storeManager;
    @Resource
    private Validator validator;
    @Resource
    private StoreService storeService;
    @Resource
    private GoodsSkuService goodsSkuService;
    @Resource
    private GoodsAttributeValueManager attributeValueManager;
    @Resource
    private CartManager cartManager;
    @Resource
    private ILockManager lockManager;
    
    @Override
    @FieldBindHandle
    public IPage<GoodsResp> pageGoods(GoodsPageReq req) {
        TenantContext tenantContext = TenantContext.get();
        String tenantId = tenantContext.getTenantId();

        LocalDateTime beginTime = req.getBeginLocalDateTime();
        LocalDateTime endTime = req.getEndLocalDateTime();
        LambdaQueryWrapper<Goods> lqw = Goods.lqw()
            .ge(Objects.nonNull(beginTime), Goods::getCreatedAt, beginTime)
            .le(Objects.nonNull(endTime), Goods::getCreatedAt, endTime)
            .like(StringUtils.isNotEmpty(req.getKeyword()), Goods::getName, req.getKeyword())
            .orderByDesc(Goods::getCreatedAt);
        IPage<Goods> pageData = this.page(PageUtil.to(req), lqw);
        List<Goods> records = pageData.getRecords();

        // 获取添加到门店信息
        List<Long> goodsIds = records.stream().map(Goods::getId).collect(Collectors.toList());

        // 获取商品和租户(门店)的上架(映射)关系
        Map<Long, List<StoreGoods>> goodsIdAndTenantIdMapper = TenantUtils.execute(DataIsolateLevel.MAIN, tenantContext.getMainTenantId(), () -> {
            if (CollUtil.isEmpty(goodsIds)) {
                return Collections.emptyMap();
            }
            List<StoreGoods> storeGoods = this.storeGoodsManager.listByGoodsIds(goodsIds, null);
            return storeGoods.stream()
                    .collect(Collectors.groupingBy(StoreGoods::getGoodsId));
        });

        List<Long> categoryIds = records.stream().map(Goods::getCategoryId).distinct().collect(Collectors.toList());
        Map<Long, String> categoryMap = CollectionUtils.isEmpty(categoryIds) ?
                Collections.emptyMap() : categoryManager.listByIds(categoryIds).stream().collect(Collectors.toMap(GoodsCategory::getId, GoodsCategory::getName));

        return PageUtil.to(pageData, (e) -> {
            GoodsResp resp = goodsAssembly.toGoodsResp(e);
            List<StoreGoods> storeGoodsList = goodsIdAndTenantIdMapper.getOrDefault(e.getId(), Collections.emptyList());
            boolean isAddCurrentStore = storeGoodsList.stream().anyMatch(e1 -> e1.getTenantId().compareTo(tenantId) == 0);
            // 总销量
            Integer totalSaleNumber = storeGoodsList.stream().map(StoreGoods::getSaleNumber).reduce(Integer::sum).orElse(0);
            resp.setIsAddCurrentStore(isAddCurrentStore);
            resp.setCategoryName(categoryMap.get(e.getCategoryId()));
            resp.setSaleNumber(totalSaleNumber);
            return resp;
        });
    }

    @Override
    public IPage<GoodsResp> pageCurrentStoreGoods(GoodsPageReq req) {
        TenantContext tenantContext = TenantContext.get();
        String tenantId = tenantContext.getTenantId();

        LocalDateTime beginTime = req.getBeginLocalDateTime();
        LocalDateTime endTime = req.getEndLocalDateTime();
        IPage<Goods> pageData = this.baseMapper.pageStoreGoods(PageUtil.to(req), beginTime, endTime, req.getKeyword(), tenantId);
        List<Goods> records = pageData.getRecords();

        List<Long> goodsIds = records.stream().map(Goods::getId).collect(Collectors.toList());
        if (CollUtil.isEmpty(goodsIds)) {
            return new Page<>();
        }

        List<Long> categoryIds = records.stream().map(Goods::getCategoryId).distinct().collect(Collectors.toList());
        Map<Long, String> categoryMap = CollectionUtils.isEmpty(categoryIds) ?
                Collections.emptyMap() : categoryManager.listByIds(categoryIds).stream().collect(Collectors.toMap(GoodsCategory::getId, GoodsCategory::getName));

        return PageUtil.to(pageData, (e) -> {
            GoodsResp resp = goodsAssembly.toGoodsResp(e);
            resp.setIsAddCurrentStore(true);

            // 如果非总店, 就判断商品所在具体门店的发布状态
            resp.setStatus(e.getIsPublish() ? Goods.Status.LISTED : Goods.Status.NO_LISTED);
            resp.setCategoryName(categoryMap.get(e.getCategoryId()));
            return resp;
        });
    }

    @Override
    public GoodsResp getGoods(Long id) {
        AuthUserTenant currentTenant = SecurityUtils.getLoginUser().getTenant();
        Goods entity = this.baseMapper.getGoods(id);
        if (Objects.isNull(entity)) {
            throw new BizException("商品不存在");
        }

        // 查找spec数据
        LambdaQueryWrapper<GoodsSpecValue> specLqw = GoodsSpecValue.lqw().eq(GoodsSpecValue::getGoodsId, entity.getId());
        List<GoodsSpecValue> specList = goodsSpecValueManager.list(specLqw);

        // 查找sku数据
        List<GoodsSku> skuList;
        try {
            if (currentTenant.mainTenant()) {
                skuList = TenantUtils.executeIgnore(DataIsolateLevel.ALONE, () -> goodsSkuManager.list(GoodsSku.lqw().eq(GoodsSku::getGoodsId, entity.getId())));
            } else {
                skuList = goodsSkuManager.list(GoodsSku.lqw().eq(GoodsSku::getGoodsId, entity.getId()));
            }

        } catch (Exception e) {
            throw new BizException("查询sku失败");
        }

        List<SkuResp> skuRespList = listGoodsSkuList(skuList);
        GoodsResp resp = goodsAssembly.toGoodsResp(entity);

        // 查询商品规格信息, 如果是多规格, 则以最低价格进行展示
        if (resp.getMultiSpec()) {
            GoodsSku goodsSku = skuList.stream().min(Comparator.comparing(GoodsSku::getPrice))
                .orElse(new GoodsSku());
            resp.setPrice(goodsSku.getPrice());
        }

        // step 获取属性
        List<GoodsAttribute> attributeList = attributeValueManager.listAttributeValue(entity.getId());

        resp.setAttributeList(attributeList);
        resp.setSpecList(specList);
        resp.setSkuList(skuRespList);
        return resp;
    }

    private List<SkuResp> listGoodsSkuList(List<GoodsSku> goodsGoodsSku) {
        // 按照sku进行划分
        Map<String, List<GoodsSku>> skuMap = goodsGoodsSku.stream().collect(Collectors.groupingBy(e -> e.getSpecData().toString()));

        List<SkuResp> skuRespList = new ArrayList<>();
        skuMap.forEach((k, v) -> {
            GoodsSku goodsSku = v.get(0);
            SkuResp skuResp = goodsSkuAssembly.toSkuResp(goodsSku);

            List<SkuBase.Stock> stockList = v.stream().map(e -> {
                SkuBase.Stock stock = new SkuBase.Stock();
                stock.setStock(e.getStock());
                stock.setStoreId(e.getTenantId());
                return stock;
            }).collect(Collectors.toList());
            skuResp.setStockList(stockList);
            skuRespList.add(skuResp);
        });
        return skuRespList;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveGoods(GoodsSaveReq req) {
        req.setStatus(StringUtils.firstNonEmpty(req.getStatus(), Goods.Status.NO_LISTED));

        ValidationUtils.validate(validator, req, req.getMultiSpec() ? MultiSpecGoods.class : UnifySpecGoods.class);
        Goods goods = goodsAssembly.toGoods(req);

        // 处理商品
        boolean ret = this.saveOrUpdate(goods);

        List<SkuReq> skuList = req.getSkuList();

        // step 处理商品规格信息
        GoodsServiceImpl thisProxy = SpringUtils.getAopProxy(this);
        thisProxy.saveGoodsSpecValue(goods, skuList);

        // step 处理商品sku库存信息
        List<ShopStore> storeList = storeManager.list();
        this.saveSkuStock(SecurityUtils.getLoginUser(), storeList, goods, req.getSkuList(), true);

        // step 保存属性
        attributeValueManager.saveAttributeValue(req.getAttributeList(), goods.getId());
        return ret;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateGoods(GoodsUpdateReq req) {
        Goods entity = goodsAssembly.toGoods(req);

        // step 获取数据库中保存的库存规格
        List<GoodsSku> skuList = TenantUtils.execute(
            DataIsolateLevel.MAIN,
            TenantContext.get().getMainTenantId(),
            () -> goodsSkuManager.list(GoodsSku.lqw().eq(GoodsSku::getGoodsId, req.getId()))
        );

        // step 解析接收的规格值
        LambdaQueryWrapper<GoodsSpecValue> specValueLqw = GoodsSpecValue.lqw().eq(GoodsSpecValue::getGoodsId, req.getId());
        List<GoodsSpecValue> specValueList = goodsSpecValueManager.list(specValueLqw);
        Set<String> oldSpecValues = specValueList.stream().flatMap(e -> e.getValues().stream()).collect(Collectors.toSet());
        Set<String> newSpecValues = req.getSpecList().stream().flatMap(e -> e.getValues().stream()).collect(Collectors.toSet());

        // step 获取被删除的规格
        Set<String> deletedSpecValues = new HashSet<>(oldSpecValues);
        deletedSpecValues.removeAll(newSpecValues);

        // step 如果被删除的规格, 库存数大于0, 则报错提示
        Integer deletedSpecTotalStock = skuList.stream()
                .filter(e -> e.getSpecData().stream().anyMatch(e1 -> deletedSpecValues.contains(e1.getValue())))
                .map(GoodsSku::getStock)
                .reduce(0, Integer::sum);

        if (deletedSpecTotalStock > 0) {
            throw new BizException("需要商品所有规格的库存都为0时，才能删除规格。如需添加，请先调整库存");
        }

        // step 获取新增的规格, 满足所有规格库存为0时才能添加规格
        Set<String> addSpecValues = new HashSet<>(newSpecValues);
        addSpecValues.removeAll(oldSpecValues);
        boolean skuUpdated = false;
        if (CollectionUtils.isNotEmpty(addSpecValues)) {
            skuUpdated = true;
            // 计算库存总值
            Integer totalStock = skuList.stream().map(GoodsSku::getStock).reduce(0, Integer::sum);
            if (totalStock > 0) {
                throw new BizException("需要商品所有规格的库存都为0时，才能添加规格。如需添加，请先调整库存");
            }
        }

        // step 删除商品规格信息并重新创建
        GoodsServiceImpl thisProxy = SpringUtils.getAopProxy(this);
        if (req.getMultiSpec()) {
            goodsSpecValueManager.remove(GoodsSpecValue.lqw().eq(GoodsSpecValue::getGoodsId, req.getId()));
            thisProxy.saveGoodsSpecValue(entity, req.getSkuList());
        }

        // step 删除sku信息并重新创建
        if (skuUpdated) {
            List<ShopStore> storeList = storeManager.list();
            goodsSkuManager.deleteByGoodsId(entity.getId());
            thisProxy.saveSkuStock(SecurityUtils.getLoginUser(), storeList, entity, req.getSkuList(), skuUpdated);
        }

        // step 保存属性
        attributeValueManager.saveAttributeValue(req.getAttributeList(), entity.getId());

        return this.updateById(entity);
    }


    public void saveGoodsSpecValue(Goods goods, List<SkuReq> skuList ) {
        List<GoodsSpecValue> specValueList = new ArrayList<>();
        Map<Long, List<GoodsSku.Spec>> specValueMap = skuList.stream()
            .flatMap(e -> e.getSpecData().stream())
            .peek(e -> {
                // 如果是单规格, 数据只有一条, 这里固定设置为0
                if (!goods.getMultiSpec()) {
                    e.setSpecId(0L);
                }
            }).collect(Collectors.groupingBy(GoodsSku.Spec::getSpecId));
        specValueMap.forEach((k, v) -> {
            GoodsSpecValue specValue = new GoodsSpecValue();
            String specName = v.stream().findFirst().map(GoodsSku.Spec::getKey).orElse("");
            specValue.setValues(v.stream().map(GoodsSku.Spec::getValue).distinct().collect(Collectors.toList()))
                    .setGoodsId(goods.getId()).setSpecId(k).setName(specName);
            specValueList.add(specValue);
        });
        goodsSpecValueManager.saveBatch(specValueList);
        log.info("batch save spec value, size: {}, goodsId: {}", specValueList.size(), goods.getId());
    }


    public void saveSkuStock(LoginUser loginUser, List<ShopStore> storeList,
                             Goods goods, List<SkuReq> skuList, boolean skuUpdated) {
        if (!skuUpdated) {
            return;
        }
        ILock lock = lockManager.getLock(String.format("lock:goods_write:%s", goods.getId()));
        lock.lock();
        try {
            Map<Long, List<GoodsSku>> skuOfIdMap = new HashMap<>();
            Map<String, List<GoodsSku>> skuMap = skuList.stream().flatMap(e -> {
                // 循环保存每个门店sku库存
                if (CollectionUtils.isEmpty(e.getStockList())) {
                    return storeList.stream().map(stock -> {
                        GoodsSku goodsSku = goodsSkuAssembly.toSku(e).setGoodsId(goods.getId())
                            .setId(YitIdHelper.nextId()).setStock(0);
                        goodsSku.setTenantId(stock.getId());
                        return goodsSku;
                    }).collect(Collectors.toList()).stream();
                }

                return e.getStockList().stream().map(stock -> {
                    GoodsSku sku = goodsSkuAssembly.toSku(e).setGoodsId(goods.getId())
                        .setId(YitIdHelper.nextId()).setStock(ObjectUtils.defaultIfNull(stock.getStock(), 0));
                    sku.setTenantId(stock.getStoreId());
                    skuOfIdMap.computeIfAbsent(sku.getId(), k -> new ArrayList<>()).add(sku);
                    return sku;
                }).collect(Collectors.toList()).stream();
            }).collect(Collectors.groupingBy(GoodsSku::getTenantId));

            // 保存数据, 按照门店进行分组
            skuMap.forEach((k, v) -> TenantUtils.execute(k, () ->  {
                if (CollUtil.isEmpty(v)) {
                    return;
                }
                // 分批保存每个门店sku库存数据
                goodsSkuManager.saveBatch(v);
                log.info("batch save sku stock, size: {}, goodsId: {}", v.size(), goods.getId());

                // step 处理商品库存单据记录
                // 计算成本金额
                BigDecimal totalCostPrice = v.stream().map(e -> e.getCostPrice().multiply(new BigDecimal(e.getStock())))
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

                StockDocument stockDocument = new StockDocument().setStatus(StockInOutStatus.DO_EXAMINE)
                    .setStockNo(String.valueOf(YitIdHelper.nextId())).setOperateTime(new Date())
                    .setAuditTime(new Date()).setTotalPrice(totalCostPrice).setType(StockInOutType.IN_INIT.getKey())
                    .setOperaterId(loginUser.getUserId()).setOperaterName(loginUser.getUserName());
                stockDocumentManager.save(stockDocument);
                log.info("save stock in record, size: {}, goodsId: {}", v.size(), goods.getId());

                // step 处理商品库存单据详情
                List<StockDocumentDetail> inOutDetailList = v.stream().map(e -> {
                    // 当前规格的所有门店总库存
                    BigDecimal afterStock = skuOfIdMap.get(e.getId()).stream().map(GoodsSku::getStock).reduce(Integer::sum)
                        .map(BigDecimal::valueOf).orElse(BigDecimal.ZERO);
                    return new StockDocumentDetail().setGoodsSkuId(e.getId()).setGoodsId(goods.getId())
                        .setGoodsSkuId(e.getId()).setGoodsSkuNo(e.getSkuCode()).setGoodsSkuSpec(JSON.toJSONString(e.getSpecData()))
                        .setDocumentId(stockDocument.getId()).setBeforeStoreStock(BigDecimal.ZERO)
                        .setAfterStock(afterStock).setAfterStoreStock(BigDecimal.valueOf(e.getStock()))
                        .setBeforeStoreStock(BigDecimal.ZERO)
                        .setCostPrice(e.getCostPrice()).setTotalCostPrice(totalCostPrice)
                        .setOperaterId(loginUser.getUserId()).setOperaterName(loginUser.getUserName())
                        .setOperaterTime(new Date());
                }).filter(e -> e.getAfterStock().compareTo(BigDecimal.ZERO) > 0).collect(Collectors.toList());
                stockDocumentDetailManager.saveBatch(inOutDetailList);
                log.info("save stock in detail, size: {}, goodsId: {}", inOutDetailList.size(), goods.getId());
            }));
        } finally {
            lock.unlock();
        }
    }

    @Override
    public boolean deleteGoods(Long id) {
        return SpringUtils.getAopProxy(this).batchDeleteGoods(Collections.singletonList(id));
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean batchDeleteGoods(List<Long> ids) {
        this.goodsSkuManager.deleteByGoodsIds(ids);
    	return this.removeByIds(ids);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updatePublishStatus(Long goodsId, Set<String> tenantIds) {
        String mainTenantId = TenantContext.get().getMainTenantId();
        String tenantId = TenantContext.get().getTenantId();

        String status = Goods.Status.LISTED;
        if (CollUtil.isEmpty(tenantIds)) {
            status = Goods.Status.NO_LISTED;
        }
        LambdaUpdateWrapper<Goods> luw = Goods.luw().eq(Goods::getId, goodsId)
                .set(Goods::getTenantIds, JSON.toJSONString(tenantIds))
                .set(Goods::getStatus, status);
        this.update(luw);

        // 处理商品和门店之间的关系
        List<StoreGoods> allStoreGoods = TenantUtils.execute(
            DataIsolateLevel.MAIN,
            mainTenantId, () -> storeGoodsManager.list(StoreGoods.lqw().eq(StoreGoods::getGoodsId, goodsId))
        );

        Set<String> oldStoreGoodsTenantIds = allStoreGoods.stream().map(e -> String.valueOf(e.getTenantId())).collect(Collectors.toSet());
        // 新增发布的门店
        Set<String> newPublishSet = new HashSet<>(tenantIds);
        newPublishSet.removeAll(oldStoreGoodsTenantIds);

        // 插入或更新数据
        for (String item : oldStoreGoodsTenantIds) {
            TenantUtils.execute(item, () -> {
                LambdaUpdateWrapper<StoreGoods> storeGoodsLqw = StoreGoods.luw().eq(StoreGoods::getGoodsId, goodsId)
                        .set(StoreGoods::getIsPublish, tenantIds.contains(item));
                storeGoodsManager.update(storeGoodsLqw);
            });
        }

        for (String newPublishTenantId : newPublishSet) {
            TenantUtils.execute(newPublishTenantId, () -> {
                StoreGoods storeGoods = new StoreGoods(null, true, goodsId, null, 0);
                storeGoodsManager.save(storeGoods);
            });
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateStoreGoodsPublishStatus(List<Long> goodsIds, Boolean isPublish) {
        TenantContext tenantContext = TenantContext.get();
        String tenantId = String.valueOf(tenantContext.getTenantId());
        if (Objects.equals(tenantContext.getMainTenantId(), tenantContext.getTenantId())) {
            throw new BizException("请切换到子门店进行操作");
        }

        List<Goods> goods = this.listByIds(goodsIds);
        storeGoodsManager.deleteByGoodIds(goodsIds);
        goods.forEach(e -> {
            Set<String> tenantIds = new HashSet<>(Optional.ofNullable(JSON.parseArray(e.getTenantIds(), String.class)).orElse(new ArrayList<>()));
            tenantIds.remove(tenantId);
            if (isPublish) {
                e.setStatus(Goods.Status.LISTED);
                tenantIds.add(tenantId);
            }
            e.setTenantIds(JSON.toJSONString(tenantIds));

            // 如果没有上架到任何门店, 则设置成已下架状态
            if (CollUtil.isEmpty(tenantIds)) {
                e.setStatus(Goods.Status.NO_LISTED);
            }

            TenantUtils.execute(tenantId, () -> {
                StoreGoods storeGoods = new StoreGoods(null, isPublish, e.getId(), null, 0);
                storeGoodsManager.save(storeGoods);
            });
        });

        this.saveOrUpdateBatch(goods);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchAddIntoCurrentStore(Set<Long> goodsIds) {
        TenantContext tenantContext = TenantContext.get();
        String mainTenantId = tenantContext.getMainTenantId();
        if (Objects.equals(tenantContext.getMainTenantId(), tenantContext.getTenantId())) {
            throw new BizException("请切换到子门店进行操作");
        }

        ShopStore shopStore = storeService.getTenantRefStore(tenantContext.getTenantId());
        if (Objects.isNull(shopStore)) {
            throw new BizException("未查找到门店信息");
        }

        List<StoreGoods> dbStoreGoodsList = storeGoodsManager.listByGoodsIds(new ArrayList<>(goodsIds) , null);
        if (dbStoreGoodsList.size() > 0) {
            throw new BizException("已有商品添加到本店, 无需重复添加");
        }

        List<StoreGoods> storeGoods = goodsIds.stream()
                .map(e -> new StoreGoods(null, false, e, null, 0))
                .collect(Collectors.toList());
        storeGoodsManager.saveBatch(storeGoods);

        // 更新商品上线的门店字段
        for (Long goodsId : goodsIds) {
            TenantUtils.execute(DataIsolateLevel.MAIN, mainTenantId, () -> {
                List<String> tenantIds = storeGoodsManager.listByGoodsId(goodsId, true)
                        .stream().map(StoreGoods::getTenantId).collect(Collectors.toList());

                if (CollUtil.isEmpty(tenantIds)) {
                    return;
                }
                LambdaUpdateWrapper<Goods> luw = Goods.luw().eq(Goods::getId, goodsId)
                        .set(Goods::getTenantIds, JSON.toJSONString(tenantIds))
                        .set(Goods::getStatus, Goods.Status.LISTED);
                this.update(luw);
            });

            List<GoodsAttributeValue> attributeValueList = TenantUtils.execute(DataIsolateLevel.MAIN, () -> {
                // step 拷贝总店属性信息到当前门店, 便于门店自定义属性
                return this.attributeValueManager.listByGoodsId(goodsId).stream()
                    .peek(e -> e.setId(null)).collect(Collectors.toList());
            });

            this.attributeValueManager.saveBatch(attributeValueList);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchDeleteFromCurrentStore(List<Long> goodsIds) {
        String tenantId = TenantContext.get().getTenantId();
        List<Goods> goods = this.listByIds(goodsIds);
        goods.forEach(e -> {
            Set<String> tenantIds = new HashSet<>(Optional.ofNullable(JSON.parseArray(e.getTenantIds(),  String.class)).orElse(new ArrayList<>()));
            tenantIds.remove(String.valueOf(tenantId));
            e.setTenantIds(JSON.toJSONString(tenantIds));
            if (CollUtil.isEmpty(tenantIds)) {
                e.setStatus(Goods.Status.NO_LISTED);
            }
        });
        this.updateBatchById(goods);
        storeGoodsManager.deleteByGoodIds(goodsIds);

        // 删除商品属性
        TenantUtils.execute(DataIsolateLevel.ALONE, () -> attributeValueManager.batchDeleteByGoodsIds(goodsIds));
    }

    @Override
    public List<GoodsListResp> listGoods(GoodsListReq req) {
        // 查询当前门店所有商品
        List<Goods> goodsList = this.baseMapper.listStoreGoods( true);
        List<Long> goodsIds = goodsList.stream().map(Goods::getId).collect(Collectors.toList());

        CartListReq cartReq = new CartListReq().setCartType(req.getGoodsType());
        // 如果一个商品是多规格的, 可能会添加多次且选择的规格 + 属性不一样
        Map<Long, Optional<Integer>> cartMap = cartManager.listCart(cartReq).stream()
            .collect(Collectors.groupingBy(Cart::getGoodsId, Collectors.mapping(Cart::getGoodsNum, Collectors.reducing(Integer::sum))));

        // 查询分类信息
        List<Long> categoryIds = goodsList.stream().map(Goods::getCategoryId).distinct().collect(Collectors.toList());
        categoryIds.add(-1L);

        List<GoodsCategory> categoryList = categoryManager.listByIds(categoryIds);
        Map<Long, GoodsCategory> categoryMap = categoryList.stream().collect(Collectors.toMap(GoodsCategory::getId, Function.identity()));
        Map<Long, List<GoodsResp>> goodsGroupMap = goodsList.stream().map(goodsAssembly::toGoodsResp)
                .collect(Collectors.groupingBy(GoodsResp::getCategoryId));

        // 查询商品规格库存信息
        List<GoodsSku> skuList = goodsSkuService.listSkuByGoodsIds(goodsIds);
        Map<Long, List<GoodsSku>> skuMap = skuList.stream().collect(Collectors.groupingBy(GoodsSku::getGoodsId));

        List<GoodsListResp> respList = new ArrayList<>();
        goodsGroupMap.forEach((k, v) -> {
            GoodsListResp resp = goodsAssembly.toGoodsListResp(categoryMap.getOrDefault(k, new GoodsCategory()));
            // 查询商品规格信息, 如果是多规格, 则以最低价格进行展示
            Stream<GoodsResp> goodsStream = v.stream().filter(e -> skuMap.containsKey(e.getId()) && skuMap.get(e.getId()).size() > 0);

            goodsStream.forEach(e -> {
                List<GoodsSku> goodsSkuList = skuMap.get(e.getId());
                List<SkuResp> skuRespList = listGoodsSkuList(goodsSkuList);
                GoodsSku goodsSku = goodsSkuList.stream().filter(e1 -> e1.getStock() > 0).min(Comparator.comparing(GoodsSku::getPrice))
                        .orElse(null);
                if (Objects.isNull(goodsSku)) {
                    return;
                }
                e.setSkuList(skuRespList);
                e.setPrice(goodsSku.getPrice());
                e.setBuyNum(cartMap.getOrDefault(e.getId(), Optional.empty()).orElse(0));
            });

            List<GoodsResp> goodsRespList = v.stream().filter(e -> skuMap.containsKey(e.getId()))
                .filter(e -> skuMap.get(e.getId()).stream().anyMatch(e1 -> e1.getStock() > 0)).collect(Collectors.toList());
            resp.setGoods(goodsRespList);
            respList.add(resp);
        });
        return respList;
    }
}
